/**
 * Copyright (c) 2013 Mark Tielemans
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * DISCLAIMER
 * This software is provided "as is" and any expressed or implied warranties, including, but not limited to, the implied warranties of 
 * merchantability and fitness for a particular purpose are disclaimed. In no event shall the regents or contributors be liable for any 
 * direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute 
 * goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in 
 * contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if 
 * advised of the possibility of such damage.
 */
package nl.marktielemans.easydb;

import nl.marktielemans.easydb.connection.DatabaseConnector;
import nl.marktielemans.easydb.generator.QueryGenerator;

import org.apache.log4j.Logger;

/**
 * Used for configuration of database and query settings. {@link DatabaseConnector} and 
 * {@link QueryGenerator} can be automatically resolved by vendor id. When setting the database vendor, 
 * EasyDB will try to automatically resolve these settings if they are not set already set.
 * 
 * @author Mark Tielemans
 * @since 1.0
 * @version 1.0
 * @see DatabaseVendor
 * @see EasyDBConfig#autoResolveSettings()
 */
public class EasyDBConfig {

	private static final Logger logger = Logger.getLogger(EasyDBConfig.class);
	private static EasyDBConfig instance;

	private DatabaseConnector connector;
	private QueryGenerator queryGenerator;
	private String dbHostUrl;
	private String dbHostPort;
	private String dbName;
	private String dbUsername;
	private String dbPassword;
	private String dbVendor;
	private String dbDateFormat;

	private EasyDBConfig() {}

	/**
	 * Retrieve the working {@link EasyDBConfig} instance.
	 * @return instance
	 */
	public static EasyDBConfig get() {
		if(instance==null) instance = new EasyDBConfig();
		return instance;
	}

	/**
	 * Set the {@link QueryGenerator} to be used. Instead of setting this manually, it can also be 
	 * resolved from a known {@link DatabaseVendor} by its vendorId using {@link #setDbVendor(String)}.
	 * <br /><br />
	 * This method may be used to override auto resolved settings.
	 * @param queryGenerator the generator to be used
	 * @see #setDbVendor(String)
	 */
	public void setQueryGenerator(QueryGenerator queryGenerator) {
		logger.info("Selecting query generator: "+queryGenerator.getClass().getName());
		this.queryGenerator = queryGenerator;
	}

	/**
	 * Get the set {@link QueryGenerator}. May be <code>null</code> if not explicitly set or 
	 * auto-resolved by {@link #autoResolveSettings()}.
	 * @return set queryGenerator 
	 */
	public QueryGenerator getQueryGenerator() {
		return this.queryGenerator;
	}

	/**
	 * Get the URL for the database host. May be <code>null</code> if not explicitly set.
	 * @return the url of the database host server
	 */
	public String getDbHostUrl() {
		return this.dbHostUrl;
	}

	/**
	 * Set the URL for the database host. The host URL must always be set explicitly before connecting.
	 * @param dbHostUrl the url of the database host server
	 */
	public void setDbHostUrl(String dbHostUrl) {
		logger.info("Set host url: "+dbHostUrl);
		this.dbHostUrl = dbHostUrl;
	}

	/**
	 * Get the port to be used for database connection. May be <code>null</code> if not explicitly set.
	 * @return the database host server port to be used for connection
	 */
	public String getDbHostPort() {
		return dbHostPort;
	}

	/**
	 * Set the port to be used for database connection. The host port must always be set explicitly 
	 * before connecting.
	 * @param dbHostPort the database host server port to be used for connection
	 */
	public void setDbHostPort(String dbHostPort) {
		logger.info("Set host port: "+dbHostPort);
		this.dbHostPort = dbHostPort;
	}

	/**
	 * Get the full qualified name of the database. May be <code>null</code> if not explicitly set.
	 * @return database name
	 */
	public String getDbName() {
		return dbName;
	}

	/**
	 * Set the full qualified name of the database. The database name must always be set explicitly 
	 * before connecting.
	 * @param dbName the name of the database
	 */
	public void setDbName(String dbName) {
		logger.info("Set database name: "+dbName);
		this.dbName = dbName;
	}

	/**
	 * Get the username to use for database connection. May be <code>null</code> if not explicitly set.
	 * @return username for connection
	 */
	public String getDbUsername() {
		return dbUsername;
	}

	/**
	 * Set the username for database connection. The username must always be explicitly set before 
	 * connecting.
	 * @param dbUsername the username to be used for connection to the database
	 */
	public void setDbUsername(String dbUsername) {
		logger.info("Set database user: "+dbUsername);
		this.dbUsername = dbUsername;
	}

	/**
	 * Get the password for database connection. May be <code>null</code> if not explicitly set.
	 * @return password for database connection
	 */
	public String getDbPassword() {
		return dbPassword;
	}

	/**
	 * Set the password for database connection. The password must always be explicitly set before 
	 * connecting. 
	 * @param dbPassword password to be used for database connection
	 */
	public void setDbPassword(String dbPassword) {
		logger.info("Database user password set");
		this.dbPassword = dbPassword;
	}

	/**
	 * Get the vendor id of the database vendor. May be <code>null</code> if not explicitly set.
	 * @return vendor id of the database vendor
	 */
	public String getDbVendor() {
		return dbVendor;
	}

	/**
	 * Set the vendor id of the database vendor. If the {@link QueryGenerator} or 
	 * {@link DatabaseConnector} have not yet been set, an attempt will be made to automatically 
	 * resolve them using {@link #autoResolveSettings()}.
	 * @param dbVendor vendor id of the database vendor as known in {@link DatabaseVendor}
	 * @see DatabaseVendor#getVendors()
	 */
	public void setDbVendor(String dbVendor) {
		logger.info("Set database vendor: "+dbVendor);
		this.dbVendor = dbVendor;
		autoResolveSettings(false);
	}

	/**
	 * Get the default date format used for date values stored in the database. May be 
	 * <code>null</code> if not explicitly set.
	 * @return date format to be used
	 */
	public String getDbDateFormat() {
		return dbDateFormat;
	}

	/**
	 * Set the default date format to be used for date values stored in the database.
	 * @param dbDateFormat date format to be used
	 */
	public void setDbDateFormat(String dbDateFormat) {
		logger.info("Set database date format: "+dbDateFormat);
		this.dbDateFormat = dbDateFormat;
	}

	/**
	 * Get the connector to be used for database connection. May be <code>null</code> if not explicitly 
	 * set or auto-resolved by {@link #autoResolveSettings()}.
	 * @return
	 */
	public DatabaseConnector getConnector() {
		return this.connector;
	}

	/**
	 * Set the connector to be used for database connection. Instead of setting this manually, it can 
	 * also be resolved from a known {@link DatabaseVendor} by its vendorId using 
	 * {@link #setDbVendor(String)}.
	 * <br /><br />
	 * This method may be used to override auto resolved settings.
	 * @param connector to be used for database connection
	 */
	public void setConnector(DatabaseConnector connector) {
		logger.info("Set database connector: "+connector.getClass().getName());
		this.connector = connector;
	}

	/**
	 * Make an attempt to automatically resolve the {@link QueryGenerator} and 
	 * {@link DatabaseConnector} to use by comparing the previously set vendorId with registered 
	 * {@link DatabaseVendor}s.
	 * @return <code>true</code> if settings were changed due to the auto-resolve attempt
	 * @see DatabaseVendor#getVendors()
	 */
	public boolean autoResolveSettings() {
		return autoResolveSettings(true);
	}
	
	private boolean autoResolveSettings(boolean overwriteExisting) {
		DatabaseVendor vendor = EasyDBUtils.getVendor(this.dbVendor);
		if(vendor==null) {
			logger.info("Failed to automatically resolve vendor settings");
			return false;
		}
		if(overwriteExisting) {
			this.setQueryGenerator(vendor.getGenerator());
			this.setConnector(vendor.getConnector());
			return true;
		} else {
			boolean setNew=false;
			if(this.queryGenerator==null) {
				setNew=true;
				this.setQueryGenerator(vendor.getGenerator());
			}
			if(this.connector==null) {
				setNew=true;
				this.setConnector(vendor.getConnector());
			}
			return setNew;
		}
	}
}
